package com.lagou.guavacache.service.impl;

import com.google.common.base.Optional;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.lagou.guavacache.dto.ModifyPostDTO;
import com.lagou.guavacache.entity.PostInfo;
import com.lagou.guavacache.enums.CompanyTypeEnum;
import com.lagou.guavacache.enums.EducationTypeEnum;
import com.lagou.guavacache.enums.SeniorityTypeEnum;
import com.lagou.guavacache.mapper.PostMapper;
import com.lagou.guavacache.service.PostService;
import com.lagou.guavacache.vo.PostInfoVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

@Service
@Slf4j
public class PostServiceImpl implements PostService {

    @Autowired
    private PostMapper postMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    public static Cache<String, Optional<PostInfoVO>> hotPostsCache;
    public static Cache<String, Optional<PostInfoVO>> postCache;

    static {
        // 初始化本地缓存
        hotPostsCache = CacheBuilder.newBuilder().build();
        postCache = CacheBuilder.newBuilder().build();
    }

    /**
     * 获取首页热门岗位信息
     * 优先从本地缓存取得；如果没有，则查询redis；如果还没有，则查询数据库，并写入redis和本地缓存
     */
    @Override
    public List<PostInfoVO> getHotPosts(Integer count) {

        List<PostInfoVO> postInfoVOList = new ArrayList<>();

        hotPostsCache.asMap().forEach((e, v) -> {
            postInfoVOList.add(v.get());
        });

        if (postInfoVOList.size() == 0) {
            Set<PostInfoVO> postInfoVOSet = redisTemplate.opsForZSet().reverseRangeByScore("hot-posts", 0, count);
            if (postInfoVOSet != null) {
                postInfoVOSet.forEach(e -> {
                    postInfoVOList.add(e);
                });

            } else {
                List<PostInfo> postInfoList = postMapper.selectHotPosts(count);
                postInfoList.forEach(e -> {
                    postInfoVOList.add(editVO(e));
                });
            }
        }

        return postInfoVOList;
    }

    /**
     * 根据岗位id查询岗位信息
     * 优先从本地缓存取得；如果没有，则查询redis；如果还没有，则查询数据库，并写入redis和本地缓存
     */
    @Override
    public PostInfoVO getPostById(String postId) {

        log.info("PostServiceImpl.getPostById()");

        final boolean[] isLoadCache = {true};

        PostInfoVO postInfoVO = new PostInfoVO();
        Optional<PostInfoVO> cachePostInfo = null;

        // 1.从本地缓存取得岗位信息
        try {
            cachePostInfo = (Optional<PostInfoVO>) postCache.get(postId, new Callable<Optional<PostInfoVO>>() {
                // 2.如果没有，则查询redis
                @Override
                public Optional<PostInfoVO> call() throws Exception {

                    isLoadCache[0] = false;
                    log.info("从本地缓存中未获取到数据：" + postId);

                    Object redisPostInfo = redisTemplate.opsForHash().get("post", postId);

                    if (redisPostInfo != null) {

                        log.info("从redis中获取数据：" + postId);
                        log.info("已存入本地缓存：" + postId);
                        return Optional.of((PostInfoVO) redisPostInfo);
                    } else {
                        log.info("从redis中未获取到数据：" + postId);
                        return Optional.fromNullable(null);
                    }
                }
            });
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        // 如果本地缓存中存在该岗位信息，则取出返回客户端
        if (cachePostInfo.isPresent()) {
            postInfoVO = cachePostInfo.get();

            if (isLoadCache[0]) {
                log.info("从本地缓存中获取数据：" + postId);
            }

        } else {
            // 3.如果还没有，则查询数据库
            PostInfo postInfo = postMapper.selectPostById(Long.valueOf(postId));
            log.info("从数据库中获取数据：" + postId);
            if (postInfo != null) {
                postInfoVO = editVO(postInfo);
                // 写入redis和本地缓存
                redisTemplate.opsForHash().put("post", postInfoVO.getPostId(), postInfoVO);
                log.info("已存入redis缓存：" + postId);

                postCache.put(postInfoVO.getPostId(), Optional.of(postInfoVO));
                log.info("已存入本地缓存：" + postId);
            }
        }

        return postInfoVO;
    }

    /**
     * 根据岗位id修改岗位信息
     * 修改后删除缓存中旧的数据
     */
    @Override
    public void modifyPostById(String postId, ModifyPostDTO modifyPostDTO) {

        log.info("PostServiceImpl.modifyPostById()");

        postMapper.updatePostById(Long.valueOf(postId), modifyPostDTO);
        log.info("数据库已修改：" + postId);

        redisTemplate.opsForHash().delete("post", postId);
        log.info("redis缓存已清除：" + postId);

        postCache.invalidate(postId);
        log.info("本地缓存已清除：" + postId);
    }

    /**
     * 刷新热门岗位信息
     * 演示时取得最热门的10条（实际应用时可取得500条）
     */
    @Override
    public void refreshHotPosts() {
        List<PostInfo> postInfoList = postMapper.selectHotPosts(10);

        Cache<String, Optional<PostInfoVO>> newHotPostsCache = CacheBuilder.newBuilder().build();
        redisTemplate.opsForZSet().getOperations().unlink("hot-posts");
        postInfoList.forEach(e -> {
            PostInfoVO postInfoVO = editVO(e);
            redisTemplate.opsForZSet().add("hot-posts", postInfoVO, e.getScore());
            newHotPostsCache.put(postInfoVO.getPostId(), Optional.of(postInfoVO));
        });

        hotPostsCache = newHotPostsCache;
    }

    /**
     * 根据页面展现格式的要求编辑数据库中的信息
     */
    private PostInfoVO editVO(PostInfo postInfo) {

        PostInfoVO postInfoVO = new PostInfoVO();

        postInfoVO.setPostId(String.valueOf(postInfo.getPostId()));
        postInfoVO.setPostName(postInfo.getPostName());
        postInfoVO.setSalaryMin(String.valueOf(postInfo.getSalaryMin()));
        postInfoVO.setSalaryMax(String.valueOf(postInfo.getSalaryMax()));
        postInfoVO.setPublishDate(String.valueOf(postInfo.getPublishDate()));
        postInfoVO.setSeniority(SeniorityTypeEnum.getNameByCode(postInfo.getSeniority()));
        postInfoVO.setEducation(EducationTypeEnum.getNameByCode(postInfo.getEducation()));
        postInfoVO.setAddress(postInfo.getAddress());
        postInfoVO.setPostLabels(postInfo.getPostLabels());
        postInfoVO.setCompanyName(postInfo.getCompanyName());
        postInfoVO.setCompanyType(CompanyTypeEnum.getNameByCode(postInfo.getCompanyType()));
        postInfoVO.setCompanyIndustry(postInfo.getCompanyIndustry());
        postInfoVO.setCompanyLogo(postInfo.getCompanyLogo());

        return postInfoVO;
    }
}
